/*
 * Copyright (c) 2001-2008 Caucho Technology, Inc.  All rights reserved.
 *
 * The Apache Software License, Version 1.1
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Caucho Technology (http://www.caucho.com/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "Hessian", "Resin", and "Caucho" must not be used to
 *    endorse or promote products derived from this software without prior
 *    written permission. For written permission, please contact
 *    info@caucho.com.
 *
 * 5. Products derived from this software may not be called "Resin"
 *    nor may "Resin" appear in their names without prior written
 *    permission of Caucho Technology.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL CAUCHO TECHNOLOGY OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author Scott Ferguson
 */

package com.caucho.burlap.io;

import com.caucho.hessian.io.Deserializer;
import com.caucho.hessian.io.HessianRemoteResolver;
import com.caucho.hessian.io.SerializerFactory;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.TimeZone;

/**
 * Input stream for Burlap requests.
 *
 * <p>
 * BurlapInput is unbuffered, so any client needs to provide its own buffering.
 *
 * <pre>
 * InputStream is = ...; // from http connection
 * BurlapInput in = new BurlapInput(is);
 * String value;
 *
 * in.startReply();         // read reply header
 * value = in.readString(); // read string value
 * in.completeReply();      // read reply footer
 * </pre>
 */
public class BurlapInput extends AbstractBurlapInput {
	private static int[] base64Decode;

	public final static int TAG_EOF = -1;

	public final static int TAG_NULL = 0;
	public final static int TAG_BOOLEAN = 1;
	public final static int TAG_INT = 2;
	public final static int TAG_LONG = 3;
	public final static int TAG_DOUBLE = 4;
	public final static int TAG_DATE = 5;
	public final static int TAG_STRING = 6;
	public final static int TAG_XML = 7;
	public final static int TAG_BASE64 = 8;
	public final static int TAG_MAP = 9;
	public final static int TAG_LIST = 10;
	public final static int TAG_TYPE = 11;
	public final static int TAG_LENGTH = 12;

	public final static int TAG_REF = 13;
	public final static int TAG_REMOTE = 14;

	public final static int TAG_CALL = 15;
	public final static int TAG_REPLY = 16;
	public final static int TAG_FAULT = 17;
	public final static int TAG_METHOD = 18;
	public final static int TAG_HEADER = 19;

	public final static int TAG_NULL_END = TAG_NULL + 100;
	public final static int TAG_BOOLEAN_END = TAG_BOOLEAN + 100;
	public final static int TAG_INT_END = TAG_INT + 100;
	public final static int TAG_LONG_END = TAG_LONG + 100;
	public final static int TAG_DOUBLE_END = TAG_DOUBLE + 100;
	public final static int TAG_DATE_END = TAG_DATE + 100;
	public final static int TAG_STRING_END = TAG_STRING + 100;
	public final static int TAG_XML_END = TAG_XML + 100;
	public final static int TAG_BASE64_END = TAG_BASE64 + 100;
	public final static int TAG_MAP_END = TAG_MAP + 100;
	public final static int TAG_LIST_END = TAG_LIST + 100;
	public final static int TAG_TYPE_END = TAG_TYPE + 100;
	public final static int TAG_LENGTH_END = TAG_LENGTH + 100;

	public final static int TAG_REF_END = TAG_REF + 100;
	public final static int TAG_REMOTE_END = TAG_REMOTE + 100;

	public final static int TAG_CALL_END = TAG_CALL + 100;
	public final static int TAG_REPLY_END = TAG_REPLY + 100;
	public final static int TAG_FAULT_END = TAG_FAULT + 100;
	public final static int TAG_METHOD_END = TAG_METHOD + 100;
	public final static int TAG_HEADER_END = TAG_HEADER + 100;

	private static HashMap _tagMap;

	private static Field _detailMessageField;

	protected SerializerFactory _serializerFactory;

	protected ArrayList _refs;

	// the underlying input stream
	private InputStream _is;
	// a peek character
	protected int _peek = -1;

	// the method for a call
	private String _method;

	private int _peekTag;

	private Throwable _replyFault;

	protected StringBuffer _sbuf = new StringBuffer();
	protected StringBuffer _entityBuffer = new StringBuffer();

	protected Calendar _utcCalendar;
	protected Calendar _localCalendar;

	/**
	 * Creates an uninitialized Burlap input stream.
	 */
	public BurlapInput() {
	}

	/**
	 * Creates a new Burlap input stream, initialized with an underlying input
	 * stream.
	 *
	 * @param is
	 *            the underlying input stream.
	 */
	public BurlapInput(InputStream is) {
		init(is);
	}

	/**
	 * Sets the serializer factory.
	 */
	public void setSerializerFactory(SerializerFactory factory) {
		_serializerFactory = factory;
	}

	/**
	 * Gets the serializer factory.
	 */
	public SerializerFactory getSerializerFactory() {
		return _serializerFactory;
	}

	/**
	 * Initialize the burlap stream with the underlying input stream.
	 */
	public void init(InputStream is) {
		_is = is;
		_method = null;
		_peek = -1;
		_peekTag = -1;
		_refs = null;
		_replyFault = null;

		if (_serializerFactory == null)
			_serializerFactory = new SerializerFactory();
	}

	/**
	 * Returns the calls method
	 */
	public String getMethod() {
		return _method;
	}

	/**
	 * Returns any reply fault.
	 */
	public Throwable getReplyFault() {
		return _replyFault;
	}

	/**
	 * Starts reading the call
	 *
	 * <pre>
	 * &lt;burlap:call>
	 * &lt;method>method&lt;/method>
	 * </pre>
	 */
	public void startCall() throws IOException {
		readCall();

		while ((readHeader() != null))
			readObject();

		readMethod();
	}

	/**
	 * Starts reading the call
	 *
	 * <p>
	 * A successful completion will have a single value:
	 *
	 * <pre>
	 * &lt;burlap:call>
	 * </pre>
	 */
	public int readCall() throws IOException {
		expectTag(TAG_CALL);

		int major = 1;
		int minor = 0;

		return (major << 16) + minor;
	}

	/**
	 * Reads the method
	 *
	 * <pre>
	 * &lt;method>method&lt;/method>
	 * </pre>
	 */
	public String readMethod() throws IOException {
		expectTag(TAG_METHOD);

		_method = parseString();

		expectTag(TAG_METHOD_END);

		return _method;
	}

	/**
	 * Completes reading the call
	 *
	 * <p>
	 * A successful completion will have a single value:
	 *
	 * <pre>
	 * &lt;/burlap:call>
	 * </pre>
	 */
	public void completeCall() throws IOException {
		expectTag(TAG_CALL_END);
	}

	/**
	 * Reads a reply as an object. If the reply has a fault, throws the
	 * exception.
	 */
	public Object readReply(Class expectedClass) throws Throwable {
		expectTag(TAG_REPLY);

		int tag = parseTag();

		if (tag == TAG_FAULT)
			throw prepareFault();
		else {
			_peekTag = tag;
			Object value = readObject(expectedClass);

			expectTag(TAG_REPLY_END);

			return value;
		}
	}

	/**
	 * Starts reading the reply
	 *
	 * <p>
	 * A successful completion will have a single value:
	 *
	 * <pre>
	 * &lt;burlap:reply>
	 * &lt;value>
	 * </pre>
	 */
	public void startReply() throws Throwable {
		expectTag(TAG_REPLY);

		int tag = parseTag();
		if (tag == TAG_FAULT)
			throw prepareFault();
		else
			_peekTag = tag;
	}

	/**
	 * Prepares the fault.
	 */
	private Throwable prepareFault() throws IOException {
		HashMap fault = readFault();

		Object detail = fault.get("detail");
		String message = (String) fault.get("message");

		if (detail instanceof Throwable) {
			_replyFault = (Throwable) detail;

			if (message != null && _detailMessageField != null) {
				try {
					_detailMessageField.set(_replyFault, message);
				} catch (Throwable e) {
				}
			}

			return _replyFault;
		}

		else {
			String code = (String) fault.get("code");

			_replyFault = new BurlapServiceException(message, code, detail);

			return _replyFault;
		}
	}

	/**
	 * Completes reading the call
	 *
	 * <p>
	 * A successful completion will have a single value:
	 *
	 * <pre>
	 * &lt;/burlap:reply>
	 * </pre>
	 */
	public void completeReply() throws IOException {
		expectTag(TAG_REPLY_END);
	}

	/**
	 * Reads a header, returning null if there are no headers.
	 *
	 * <pre>
	 * &lt;header>value&lt;/header>
	 * </pre>
	 */
	public String readHeader() throws IOException {
		int tag = parseTag();

		if (tag == TAG_HEADER) {
			_sbuf.setLength(0);
			String value = parseString(_sbuf).toString();
			expectTag(TAG_HEADER_END);
			return value;
		}

		_peekTag = tag;

		return null;
	}

	/**
	 * Reads a null
	 *
	 * <pre>
	 * &lt;null>&lt;/null>
	 * </pre>
	 */
	public void readNull() throws IOException {
		int tag = parseTag();

		switch (tag) {
		case TAG_NULL:
			expectTag(TAG_NULL_END);
			return;

		default:
			throw expectedTag("null", tag);
		}
	}

	/**
	 * Reads a boolean
	 *
	 * <pre>
	 * &lt;boolean>0&lt;/boolean>
	 * &lt;boolean>1&lt;/boolean>
	 * </pre>
	 */
	public boolean readBoolean() throws IOException {
		int tag = parseTag();

		boolean value;

		switch (tag) {
		case TAG_NULL:
			value = false;
			expectTag(TAG_NULL_END);
			return value;

		case TAG_BOOLEAN:
			value = parseInt() != 0;
			expectTag(TAG_BOOLEAN_END);
			return value;

		case TAG_INT:
			value = parseInt() != 0;
			expectTag(TAG_INT_END);
			return value;

		case TAG_LONG:
			value = parseLong() != 0;
			expectTag(TAG_LONG_END);
			return value;

		case TAG_DOUBLE:
			value = parseDouble() != 0;
			expectTag(TAG_DOUBLE_END);
			return value;

		default:
			throw expectedTag("boolean", tag);
		}
	}

	/**
	 * Reads a byte
	 *
	 * <pre>
	 * &lt;int>value&lt;/int>
	 * </pre>
	 */
	public byte readByte() throws IOException {
		return (byte) readInt();
	}

	/**
	 * Reads a short
	 *
	 * <pre>
	 * &lt;int>value&lt;/int>
	 * </pre>
	 */
	public short readShort() throws IOException {
		return (short) readInt();
	}

	/**
	 * Reads an integer
	 *
	 * <pre>
	 * &lt;int>value&lt;/int>
	 * </pre>
	 */
	public int readInt() throws IOException {
		int tag = parseTag();

		int value;

		switch (tag) {
		case TAG_NULL:
			value = 0;
			expectTag(TAG_NULL_END);
			return value;

		case TAG_BOOLEAN:
			value = parseInt();
			expectTag(TAG_BOOLEAN_END);
			return value;

		case TAG_INT:
			value = parseInt();
			expectTag(TAG_INT_END);
			return value;

		case TAG_LONG:
			value = (int) parseLong();
			expectTag(TAG_LONG_END);
			return value;

		case TAG_DOUBLE:
			value = (int) parseDouble();
			expectTag(TAG_DOUBLE_END);
			return value;

		default:
			throw expectedTag("int", tag);
		}
	}

	/**
	 * Reads a long
	 *
	 * <pre>
	 * &lt;long>value&lt;/long>
	 * </pre>
	 */
	public long readLong() throws IOException {
		int tag = parseTag();

		long value;

		switch (tag) {
		case TAG_NULL:
			value = 0;
			expectTag(TAG_NULL_END);
			return value;

		case TAG_BOOLEAN:
			value = parseInt();
			expectTag(TAG_BOOLEAN_END);
			return value;

		case TAG_INT:
			value = parseInt();
			expectTag(TAG_INT_END);
			return value;

		case TAG_LONG:
			value = parseLong();
			expectTag(TAG_LONG_END);
			return value;

		case TAG_DOUBLE:
			value = (long) parseDouble();
			expectTag(TAG_DOUBLE_END);
			return value;

		default:
			throw expectedTag("long", tag);
		}
	}

	/**
	 * Reads a float
	 *
	 * <pre>
	 * &lt;double>value&lt;/double>
	 * </pre>
	 */
	public float readFloat() throws IOException {
		return (float) readDouble();
	}

	/**
	 * Reads a double
	 *
	 * <pre>
	 * &lt;double>value&lt;/double>
	 * </pre>
	 */
	public double readDouble() throws IOException {
		int tag = parseTag();

		double value;

		switch (tag) {
		case TAG_NULL:
			value = 0;
			expectTag(TAG_NULL_END);
			return value;

		case TAG_BOOLEAN:
			value = parseInt();
			expectTag(TAG_BOOLEAN_END);
			return value;

		case TAG_INT:
			value = parseInt();
			expectTag(TAG_INT_END);
			return value;

		case TAG_LONG:
			value = parseLong();
			expectTag(TAG_LONG_END);
			return value;

		case TAG_DOUBLE:
			value = parseDouble();
			expectTag(TAG_DOUBLE_END);
			return value;

		default:
			throw expectedTag("double", tag);
		}
	}

	/**
	 * Reads a date.
	 *
	 * <pre>
	 * &lt;date>ISO-8609 date&lt;/date>
	 * </pre>
	 */
	public long readUTCDate() throws IOException {
		int tag = parseTag();

		if (tag != TAG_DATE)
			throw error("expected date");

		if (_utcCalendar == null)
			_utcCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));

		long value = parseDate(_utcCalendar);

		expectTag(TAG_DATE_END);

		return value;
	}

	/**
	 * Reads a date.
	 *
	 * <pre>
	 * &lt;date>ISO-8609 date&lt;/date>
	 * </pre>
	 */
	public long readLocalDate() throws IOException {
		int tag = parseTag();

		if (tag != TAG_DATE)
			throw error("expected date");

		if (_localCalendar == null)
			_localCalendar = Calendar.getInstance();

		long value = parseDate(_localCalendar);

		expectTag(TAG_DATE_END);

		return value;
	}

	/**
	 * Reads a string
	 *
	 * <pre>
	 * &lt;string>value&lt;/string>
	 * </pre>
	 */
	public String readString() throws IOException {
		int tag = parseTag();

		String value;

		switch (tag) {
		case TAG_NULL:
			expectTag(TAG_NULL_END);
			return null;

		case TAG_STRING:
			_sbuf.setLength(0);
			value = parseString(_sbuf).toString();
			expectTag(TAG_STRING_END);
			return value;

		case TAG_XML:
			_sbuf.setLength(0);
			value = parseString(_sbuf).toString();
			expectTag(TAG_XML_END);
			return value;

		default:
			throw expectedTag("string", tag);
		}
	}

	/**
	 * Reads an XML node.
	 *
	 * <pre>
	 * &xml;xml string&lt;/xml>
	 * </pre>
	 */
	public org.w3c.dom.Node readNode() throws IOException {
		int tag = read();

		switch (tag) {
		case 'N':
			return null;

		case 'S':
		case 's':
		case 'X':
		case 'x':
			throw error("can't cope");

		default:
			throw expectedTag("string", tag);
		}
	}

	/**
	 * Reads a byte array
	 *
	 * <pre>
	 * &lt;base64>...&lt;/base64>
	 * </pre>
	 */
	public byte[] readBytes() throws IOException {
		int tag = parseTag();

		switch (tag) {
		case TAG_NULL:
			expectTag(TAG_NULL_END);
			return null;

		case TAG_BASE64:
			byte[] data = parseBytes();
			expectTag(TAG_BASE64_END);

			return data;

		default:
			throw expectedTag("bytes", tag);
		}
	}

	/**
	 * Reads a length
	 *
	 * <pre>
	 * &lt;length>value&lt;/length>
	 * </pre>
	 */
	public int readLength() throws IOException {
		int tag = parseTag();

		if (tag != TAG_LENGTH) {
			_peekTag = tag;
			return -1;
		}

		int value = parseInt();

		expectTag(TAG_LENGTH_END);

		return value;
	}

	/**
	 * Reads a fault.
	 */
	private HashMap readFault() throws IOException {
		HashMap map = new HashMap();

		int code = parseTag();
		for (; code >= 0 && code != TAG_FAULT_END; code = parseTag()) {
			_peekTag = code;

			Object key = readObject();
			Object value = readObject();

			if (key != null && value != null)
				map.put(key, value);
		}

		if (code != TAG_FAULT_END)
			throw expectedTag("fault", code);

		return map;
	}

	/**
	 * Reads an object from the input stream with an expected type.
	 */
	public Object readObject(Class cl) throws IOException {
		if (cl == null || cl.equals(Object.class))
			return readObject();

		int tag = parseTag();

		switch (tag) {
		case TAG_NULL:
			expectTag(TAG_NULL_END);
			return null;

		case TAG_MAP: {
			String type = readType();
			Deserializer reader;

			reader = _serializerFactory.getObjectDeserializer(type, cl);

			return reader.readMap(this);
		}

		case TAG_LIST: {
			String type = readType();
			int length = readLength();

			Deserializer reader;
			reader = _serializerFactory.getObjectDeserializer(type, cl);

			return reader.readList(this, length);
		}

		case TAG_REF: {
			int ref = parseInt();

			expectTag(TAG_REF_END);

			return _refs.get(ref);
		}

		case TAG_REMOTE: {
			String type = readType();
			String url = readString();

			expectTag(TAG_REMOTE_END);

			Object remote = resolveRemote(type, url);

			return remote;
		}
		}

		_peekTag = tag;

		Object value = _serializerFactory.getDeserializer(cl).readObject(this);

		return value;
	}

	/**
	 * Reads an arbitrary object from the input stream when the type is unknown.
	 */
	public Object readObject() throws IOException {
		int tag = parseTag();

		switch (tag) {
		case TAG_NULL:
			expectTag(TAG_NULL_END);
			return null;

		case TAG_BOOLEAN: {
			int value = parseInt();
			expectTag(TAG_BOOLEAN_END);
			return new Boolean(value != 0);
		}

		case TAG_INT: {
			int value = parseInt();
			expectTag(TAG_INT_END);
			return new Integer(value);
		}

		case TAG_LONG: {
			long value = parseLong();
			expectTag(TAG_LONG_END);
			return new Long(value);
		}

		case TAG_DOUBLE: {
			double value = parseDouble();
			expectTag(TAG_DOUBLE_END);
			return new Double(value);
		}

		case TAG_DATE: {
			long value = parseDate();
			expectTag(TAG_DATE_END);
			return new Date(value);
		}

		case TAG_XML: {
			return parseXML();
		}

		case TAG_STRING: {
			_sbuf.setLength(0);

			String value = parseString(_sbuf).toString();

			expectTag(TAG_STRING_END);

			return value;
		}

		case TAG_BASE64: {
			byte[] data = parseBytes();

			expectTag(TAG_BASE64_END);

			return data;
		}

		case TAG_LIST: {
			String type = readType();
			int length = readLength();

			return _serializerFactory.readList(this, length, type);
		}

		case TAG_MAP: {
			String type = readType();
			Deserializer deserializer;
			deserializer = _serializerFactory.getObjectDeserializer(type);

			return deserializer.readMap(this);
		}

		case TAG_REF: {
			int ref = parseInt();

			expectTag(TAG_REF_END);

			return _refs.get(ref);
		}

		case TAG_REMOTE: {
			String type = readType();
			String url = readString();

			expectTag(TAG_REMOTE_END);

			return resolveRemote(type, url);
		}

		default:
			throw error("unknown code:" + tagName(tag));
		}
	}

	/**
	 * Reads a remote object.
	 */
	public Object readRemote() throws IOException {
		String type = readType();
		String url = readString();

		return resolveRemote(type, url);
	}

	/**
	 * Reads a reference.
	 */
	public Object readRef() throws IOException {
		return _refs.get(parseInt());
	}

	/**
	 * Reads the start of a list.
	 */
	public int readListStart() throws IOException {
		return parseTag();
	}

	/**
	 * Reads the start of a map.
	 */
	public int readMapStart() throws IOException {
		return parseTag();
	}

	/**
	 * Returns true if this is the end of a list or a map.
	 */
	public boolean isEnd() throws IOException {
		int code = parseTag();

		_peekTag = code;

		return (code < 0 || code >= 100);
	}

	/**
	 * Reads the end byte.
	 */
	public void readEnd() throws IOException {
		int code = parseTag();

		if (code < 100)
			throw error("unknown code:" + (char) code);
	}

	/**
	 * Reads the end of the map
	 */
	public void readMapEnd() throws IOException {
		expectTag(TAG_MAP_END);
	}

	/**
	 * Reads the end of the map
	 */
	public void readListEnd() throws IOException {
		expectTag(TAG_LIST_END);
	}

	/**
	 * Adds a list/map reference.
	 */
	public int addRef(Object ref) {
		if (_refs == null)
			_refs = new ArrayList();

		_refs.add(ref);

		return _refs.size() - 1;
	}

	/**
	 * Adds a list/map reference.
	 */
	public void setRef(int i, Object ref) {
		_refs.set(i, ref);
	}

	/**
	 * Resolves a remote object.
	 */
	public Object resolveRemote(String type, String url) throws IOException {
		HessianRemoteResolver resolver = getRemoteResolver();

		if (resolver != null)
			return resolver.lookup(type, url);
		else
			return new BurlapRemote(type, url);
	}

	/**
	 * Parses a type from the stream.
	 *
	 * <pre>
	 * &lt;type>type&lt;/type>
	 * </pre>
	 */
	public String readType() throws IOException {
		int code = parseTag();

		if (code != TAG_TYPE) {
			_peekTag = code;
			return "";
		}

		_sbuf.setLength(0);
		int ch;
		while ((ch = readChar()) >= 0)
			_sbuf.append((char) ch);
		String type = _sbuf.toString();

		expectTag(TAG_TYPE_END);

		return type;
	}

	/**
	 * Parses a 32-bit integer value from the stream.
	 */
	private int parseInt() throws IOException {
		int sign = 1;

		int ch = read();
		if (ch == '-') {
			sign = -1;
			ch = read();
		}

		int value = 0;
		for (; ch >= '0' && ch <= '9'; ch = read())
			value = 10 * value + ch - '0';

		_peek = ch;

		return sign * value;
	}

	/**
	 * Parses a 64-bit long value from the stream.
	 */
	private long parseLong() throws IOException {
		int sign = 1;

		int ch = read();
		if (ch == '-') {
			sign = -1;
			ch = read();
		}

		long value = 0;
		for (; ch >= '0' && ch <= '9'; ch = read())
			value = 10 * value + ch - '0';

		_peek = ch;

		return sign * value;
	}

	/**
	 * Parses a 64-bit double value from the stream.
	 *
	 * <pre>
	 * b64 b56 b48 b40 b32 b24 b16 b8
	 * </pre>
	 */
	private double parseDouble() throws IOException {
		int ch = skipWhitespace();

		_sbuf.setLength(0);

		for (; !isWhitespace(ch) && ch != '<'; ch = read())
			_sbuf.append((char) ch);

		_peek = ch;

		return new Double(_sbuf.toString()).doubleValue();
	}

	/**
	 * Parses a date value from the stream.
	 */
	protected long parseDate() throws IOException {
		if (_utcCalendar == null)
			_utcCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));

		return parseDate(_utcCalendar);
	}

	/**
	 * Parses a date value from the stream.
	 */
	protected long parseDate(Calendar calendar) throws IOException {
		int ch = skipWhitespace();

		int year = 0;
		for (int i = 0; i < 4; i++) {
			if (ch >= '0' && ch <= '9')
				year = 10 * year + ch - '0';
			else
				throw expectedChar("year", ch);

			ch = read();
		}

		int month = 0;
		for (int i = 0; i < 2; i++) {
			if (ch >= '0' && ch <= '9')
				month = 10 * month + ch - '0';
			else
				throw expectedChar("month", ch);

			ch = read();
		}

		int day = 0;
		for (int i = 0; i < 2; i++) {
			if (ch >= '0' && ch <= '9')
				day = 10 * day + ch - '0';
			else
				throw expectedChar("day", ch);

			ch = read();
		}

		if (ch != 'T')
			throw expectedChar("`T'", ch);

		ch = read();

		int hour = 0;
		for (int i = 0; i < 2; i++) {
			if (ch >= '0' && ch <= '9')
				hour = 10 * hour + ch - '0';
			else
				throw expectedChar("hour", ch);

			ch = read();
		}

		int minute = 0;
		for (int i = 0; i < 2; i++) {
			if (ch >= '0' && ch <= '9')
				minute = 10 * minute + ch - '0';
			else
				throw expectedChar("minute", ch);

			ch = read();
		}

		int second = 0;
		for (int i = 0; i < 2; i++) {
			if (ch >= '0' && ch <= '9')
				second = 10 * second + ch - '0';
			else
				throw expectedChar("second", ch);

			ch = read();
		}

		int ms = 0;
		if (ch == '.') {
			ch = read();

			while (ch >= '0' && ch <= '9') {
				ms = 10 * ms + ch - '0';

				ch = read();
			}
		}

		for (; ch > 0 && ch != '<'; ch = read()) {
		}

		_peek = ch;

		calendar.set(Calendar.YEAR, year);
		calendar.set(Calendar.MONTH, month - 1);
		calendar.set(Calendar.DAY_OF_MONTH, day);
		calendar.set(Calendar.HOUR_OF_DAY, hour);
		calendar.set(Calendar.MINUTE, minute);
		calendar.set(Calendar.SECOND, second);
		calendar.set(Calendar.MILLISECOND, ms);

		return calendar.getTime().getTime();
	}

	protected String parseString() throws IOException {
		_sbuf.setLength(0);

		return parseString(_sbuf).toString();
	}

	/**
	 * Parses a string value from the stream. The burlap object's string buffer
	 * is used for the result.
	 */
	protected StringBuffer parseString(StringBuffer sbuf) throws IOException {
		int ch;

		while ((ch = readChar()) >= 0)
			sbuf.append((char) ch);

		return sbuf;
	}

	org.w3c.dom.Node parseXML() throws IOException {
		throw error("help!");
	}

	/**
	 * Reads a character from the underlying stream.
	 */
	int readChar() throws IOException {
		int ch = read();

		if (ch == '<' || ch < 0) {
			_peek = ch;
			return -1;
		}

		if (ch == '&') {
			ch = read();

			if (ch == '#') {
				ch = read();

				if (ch >= '0' && ch <= '9') {
					int v = 0;
					for (; ch >= '0' && ch <= '9'; ch = read()) {
						v = 10 * v + ch - '0';
					}

					if (ch != ';')
						throw error("expected ';' at " + (char) ch);

					return (char) v;
				} else
					throw error("expected digit at " + (char) ch);
			} else {
				_entityBuffer.setLength(0);

				for (; ch >= 'a' && ch <= 'z'; ch = read())
					_entityBuffer.append((char) ch);

				String entity = _entityBuffer.toString();

				if (ch != ';')
					throw expectedChar("';'", ch);

				if (entity.equals("amp"))
					return '&';
				else if (entity.equals("apos"))
					return '\'';
				else if (entity.equals("quot"))
					return '"';
				else if (entity.equals("lt"))
					return '<';
				else if (entity.equals("gt"))
					return '>';
				else
					throw new BurlapProtocolException(
							"unknown XML entity &" + entity + "; at `" + (char) ch + "'");
			}
		} else if (ch < 0x80)
			return (char) ch;
		else if ((ch & 0xe0) == 0xc0) {
			int ch1 = read();
			int v = ((ch & 0x1f) << 6) + (ch1 & 0x3f);

			return (char) v;
		} else if ((ch & 0xf0) == 0xe0) {
			int ch1 = read();
			int ch2 = read();
			int v = ((ch & 0x0f) << 12) + ((ch1 & 0x3f) << 6) + (ch2 & 0x3f);

			return (char) v;
		} else
			throw new BurlapProtocolException("bad utf-8 encoding");
	}

	/**
	 * Parses a byte array.
	 */
	protected byte[] parseBytes() throws IOException {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();

		parseBytes(bos);

		return bos.toByteArray();
	}

	/**
	 * Parses a byte array.
	 */
	protected ByteArrayOutputStream parseBytes(ByteArrayOutputStream bos)
			throws IOException {
		int ch;
		for (ch = skipWhitespace(); ch >= 0 && ch != '<'; ch = skipWhitespace()) {
			int b1 = ch;
			int b2 = read();
			int b3 = read();
			int b4 = read();

			if (b4 != '=') {
				int chunk = ((base64Decode[b1] << 18) + (base64Decode[b2] << 12)
						+ (base64Decode[b3] << 6) + (base64Decode[b4]));

				bos.write(chunk >> 16);
				bos.write(chunk >> 8);
				bos.write(chunk);
			} else if (b3 != '=') {
				int chunk = ((base64Decode[b1] << 10) + (base64Decode[b2] << 4)
						+ (base64Decode[b3] >> 2));

				bos.write(chunk >> 8);
				bos.write(chunk);
			} else {
				int chunk = ((base64Decode[b1] << 2) + (base64Decode[b2] >> 4));

				bos.write(chunk);
			}
		}

		if (ch == '<')
			_peek = ch;

		return bos;
	}

	public void expectTag(int expectTag) throws IOException {
		int tag = parseTag();

		if (tag != expectTag)
			throw error("expected " + tagName(expectTag) + " at " + tagName(tag));
	}

	/**
	 * Parses a tag. Returns true if it's a start tag.
	 */
	protected int parseTag() throws IOException {
		if (_peekTag >= 0) {
			int tag = _peekTag;
			_peekTag = -1;
			return tag;
		}

		int ch = skipWhitespace();
		int endTagDelta = 0;

		if (ch != '<')
			throw expectedChar("'<'", ch);

		ch = read();
		if (ch == '/') {
			endTagDelta = 100;
			ch = _is.read();
		}

		if (!isTagChar(ch))
			throw expectedChar("tag", ch);

		_sbuf.setLength(0);
		for (; isTagChar(ch); ch = read())
			_sbuf.append((char) ch);

		if (ch != '>')
			throw expectedChar("'>'", ch);

		Integer value = (Integer) _tagMap.get(_sbuf.toString());
		if (value == null)
			throw error("Unknown tag <" + _sbuf + ">");

		return value.intValue() + endTagDelta;
	}

	/**
	 * Returns true if the character is a valid tag character.
	 */
	private boolean isTagChar(int ch) {
		return (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9'
				|| ch == ':' || ch == '-');
	}

	protected int skipWhitespace() throws IOException {
		int ch = read();

		for (; ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'; ch = read()) {
		}

		return ch;
	}

	protected boolean isWhitespace(int ch) throws IOException {
		return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
	}

	/**
	 * Reads bytes from the underlying stream.
	 */
	int read(byte[] buffer, int offset, int length) throws IOException {
		throw new UnsupportedOperationException();
	}

	int read() throws IOException {
		if (_peek >= 0) {
			int value = _peek;
			_peek = -1;
			return value;
		}

		int ch = _is.read();
		return ch;
	}

	public Reader getReader() {
		return null;
	}

	public InputStream readInputStream() {
		return null;
	}

	public InputStream getInputStream() {
		return null;
	}

	protected IOException expectBeginTag(String expect, String tag) {
		return new BurlapProtocolException("expected <" + expect + "> at <" + tag + ">");
	}

	protected IOException expectedChar(String expect, int ch) {
		if (ch < 0)
			return error("expected " + expect + " at end of file");
		else
			return error("expected " + expect + " at " + (char) ch);
	}

	protected IOException expectedTag(String expect, int tag) {
		return error("expected " + expect + " at " + tagName(tag));
	}

	protected IOException error(String message) {
		return new BurlapProtocolException(message);
	}

	protected static String tagName(int tag) {
		switch (tag) {
		case TAG_NULL:
			return "<null>";
		case TAG_NULL_END:
			return "</null>";

		case TAG_BOOLEAN:
			return "<boolean>";
		case TAG_BOOLEAN_END:
			return "</boolean>";

		case TAG_INT:
			return "<int>";
		case TAG_INT_END:
			return "</int>";

		case TAG_LONG:
			return "<long>";
		case TAG_LONG_END:
			return "</long>";

		case TAG_DOUBLE:
			return "<double>";
		case TAG_DOUBLE_END:
			return "</double>";

		case TAG_STRING:
			return "<string>";
		case TAG_STRING_END:
			return "</string>";

		case TAG_XML:
			return "<xml>";
		case TAG_XML_END:
			return "</xml>";

		case TAG_BASE64:
			return "<base64>";
		case TAG_BASE64_END:
			return "</base64>";

		case TAG_MAP:
			return "<map>";
		case TAG_MAP_END:
			return "</map>";

		case TAG_LIST:
			return "<list>";
		case TAG_LIST_END:
			return "</list>";

		case TAG_TYPE:
			return "<type>";
		case TAG_TYPE_END:
			return "</type>";

		case TAG_LENGTH:
			return "<length>";
		case TAG_LENGTH_END:
			return "</length>";

		case TAG_REF:
			return "<ref>";
		case TAG_REF_END:
			return "</ref>";

		case TAG_REMOTE:
			return "<remote>";
		case TAG_REMOTE_END:
			return "</remote>";

		case TAG_CALL:
			return "<burlap:call>";
		case TAG_CALL_END:
			return "</burlap:call>";

		case TAG_REPLY:
			return "<burlap:reply>";
		case TAG_REPLY_END:
			return "</burlap:reply>";

		case TAG_HEADER:
			return "<header>";
		case TAG_HEADER_END:
			return "</header>";

		case TAG_FAULT:
			return "<fault>";
		case TAG_FAULT_END:
			return "</fault>";

		case -1:
			return "end of file";

		default:
			return "unknown " + tag;
		}
	}

	static {
		_tagMap = new HashMap();
		_tagMap.put("null", new Integer(TAG_NULL));

		_tagMap.put("boolean", new Integer(TAG_BOOLEAN));
		_tagMap.put("int", new Integer(TAG_INT));
		_tagMap.put("long", new Integer(TAG_LONG));
		_tagMap.put("double", new Integer(TAG_DOUBLE));

		_tagMap.put("date", new Integer(TAG_DATE));

		_tagMap.put("string", new Integer(TAG_STRING));
		_tagMap.put("xml", new Integer(TAG_XML));
		_tagMap.put("base64", new Integer(TAG_BASE64));

		_tagMap.put("map", new Integer(TAG_MAP));
		_tagMap.put("list", new Integer(TAG_LIST));

		_tagMap.put("type", new Integer(TAG_TYPE));
		_tagMap.put("length", new Integer(TAG_LENGTH));

		_tagMap.put("ref", new Integer(TAG_REF));
		_tagMap.put("remote", new Integer(TAG_REMOTE));

		_tagMap.put("burlap:call", new Integer(TAG_CALL));
		_tagMap.put("burlap:reply", new Integer(TAG_REPLY));
		_tagMap.put("fault", new Integer(TAG_FAULT));
		_tagMap.put("method", new Integer(TAG_METHOD));
		_tagMap.put("header", new Integer(TAG_HEADER));
	}

	static {
		base64Decode = new int[256];
		for (int i = 'A'; i <= 'Z'; i++)
			base64Decode[i] = i - 'A';
		for (int i = 'a'; i <= 'z'; i++)
			base64Decode[i] = i - 'a' + 26;
		for (int i = '0'; i <= '9'; i++)
			base64Decode[i] = i - '0' + 52;
		base64Decode['+'] = 62;
		base64Decode['/'] = 63;
	}

	static {
		try {
			_detailMessageField = Throwable.class.getDeclaredField("detailMessage");
			_detailMessageField.setAccessible(true);
		} catch (Throwable e) {
		}
	}
}
